import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.semanticweb.owlapi.apibinding.OWLManager;
import org.semanticweb.owlapi.model.AddAxiom;
import org.semanticweb.owlapi.model.IRI;
import org.semanticweb.owlapi.model.OWLAxiom;
import org.semanticweb.owlapi.model.OWLClass;
import org.semanticweb.owlapi.model.OWLDataFactory;
import org.semanticweb.owlapi.model.OWLDataProperty;
import org.semanticweb.owlapi.model.OWLIndividual;
import org.semanticweb.owlapi.model.OWLLiteral;
import org.semanticweb.owlapi.model.OWLNamedIndividual;
import org.semanticweb.owlapi.model.OWLObjectProperty;
import org.semanticweb.owlapi.model.OWLObjectPropertyAssertionAxiom;
import org.semanticweb.owlapi.model.OWLOntology;
import org.semanticweb.owlapi.model.OWLOntologyCreationException;
import org.semanticweb.owlapi.model.OWLOntologyManager;
import org.semanticweb.owlapi.model.OWLOntologyStorageException;
import org.semanticweb.owlapi.model.SWRLAtom;
import org.semanticweb.owlapi.model.SWRLObjectPropertyAtom;
import org.semanticweb.owlapi.model.SWRLRule;
import org.semanticweb.owlapi.model.SWRLVariable;
import org.semanticweb.owlapi.reasoner.InferenceType;
import org.semanticweb.owlapi.reasoner.NodeSet;
import org.semanticweb.owlapi.reasoner.OWLReasoner;
import org.semanticweb.owlapi.reasoner.OWLReasonerFactory;
import org.semanticweb.owlapi.util.InferredAxiomGenerator;
import org.semanticweb.owlapi.util.InferredOntologyGenerator;
import org.semanticweb.owlapi.util.InferredPropertyAssertionGenerator;

import com.clarkparsia.pellet.owlapiv3.PelletReasonerFactory;


public class Recommender {

	private String baseURL = "http://cunctus.itba.edu.ar:8080/IMDb?lang=rdf&id=";
	private String URLbyName = "http://cunctus.itba.edu.ar:8080/IMDb?lang=rdf&name=";
	private String URLgetId = "http://cunctus.itba.edu.ar:8080/IMDb?lang=rdf&raw=True&name=";
	private IRI ontologyIRI = IRI.create("http://cunctus.itba.edu.ar:8002/IMDb");
	private IRI likesIRI = IRI.create("http://cunctus.itba.edu.ar:8002/IMDb#LikesTitle");
	
	private OWLOntologyManager manager;
	private OWLOntology ontology;
	private OWLDataFactory factory;
	private OWLIndividual indSpectator;
	private OWLReasonerFactory reasonerFactory = null;
	private OWLReasoner reasoner = null;
	private IRI spec;
	private File file;
	private String user;
	
	public Recommender(String spectator) throws FileNotFoundException, OWLOntologyCreationException{
		loadOntology();
		
		user = spectator;
		spec = IRI.create(ontologyIRI +"#"+spectator);
		this.file = new File("./local.owl");
	}
	
	private void loadOntology() throws FileNotFoundException, OWLOntologyCreationException{
		File file = new File("recursos"+File.separator+"base.xml");
		FileInputStream stream = new FileInputStream(file);
		this.manager = OWLManager.createOWLOntologyManager();
		this.ontology = this.manager.loadOntologyFromOntologyDocument(stream);
		this.factory = this.manager.getOWLDataFactory();
	}
	
	private void addAxioms(String url)throws OWLOntologyCreationException, MalformedURLException, IOException {
		
		URLConnection connection = new URL(url).openConnection();
		connection.setDoInput(true);
		InputStream inStream = connection.getInputStream();
		
		OWLOntology aux_ont = manager.loadOntologyFromOntologyDocument( inStream ); 
		
		for(OWLAxiom axiom : aux_ont.getAxioms()) {
			AddAxiom addAxiom = new AddAxiom(ontology, axiom);
			manager.applyChange(addAxiom);
	      }
	}
	
	public void addSpecAxioms() throws OWLOntologyStorageException{
		
		OWLClass spectator = factory.getOWLClass(IRI.create(ontologyIRI + "#Spectator"));
		OWLClass realPerson = factory.getOWLClass(IRI.create(ontologyIRI + "#RealPerson"));
		OWLClass actor = factory.getOWLClass(IRI.create(ontologyIRI + "#Actor"));
		OWLClass director = factory.getOWLClass(IRI.create(ontologyIRI + "#Director"));
		OWLClass title = factory.getOWLClass(IRI.create(ontologyIRI + "#Title"));
		
		OWLAxiom axiom = factory.getOWLSubClassOfAxiom(spectator, realPerson);
		AddAxiom addAxiom = new AddAxiom(ontology, axiom);
		
		OWLObjectProperty likesActor = factory.getOWLObjectProperty(IRI.create(ontologyIRI + "#LikesActor")); 
		AddAxiom addAxiomLikesActorDomain = new AddAxiom(ontology, factory.getOWLObjectPropertyDomainAxiom(likesActor, spectator));
		AddAxiom addAxiomLikesActorRange = new AddAxiom(ontology, factory.getOWLObjectPropertyRangeAxiom(likesActor, actor));
		
		OWLObjectProperty likesDirector = factory.getOWLObjectProperty(IRI.create(ontologyIRI + "#LikesDirector")); 
		AddAxiom addAxiomLikesDirectorDomain = new AddAxiom(ontology, factory.getOWLObjectPropertyDomainAxiom(likesDirector, spectator));
		AddAxiom addAxiomLikesDirectorRange = new AddAxiom(ontology, factory.getOWLObjectPropertyRangeAxiom(likesDirector, director));
		
		OWLObjectProperty likesTitle = factory.getOWLObjectProperty(IRI.create(ontologyIRI + "#LikesTitle")); 
		AddAxiom addAxiomLikesTitleDomain = new AddAxiom(ontology, factory.getOWLObjectPropertyDomainAxiom(likesTitle, spectator));
		AddAxiom addAxiomLikesTitleRange = new AddAxiom(ontology, factory.getOWLObjectPropertyRangeAxiom(likesTitle, title));
		
		manager.applyChange(addAxiom);
		manager.applyChange(addAxiomLikesActorDomain);
		manager.applyChange(addAxiomLikesActorRange);
		manager.applyChange(addAxiomLikesDirectorDomain);
		manager.applyChange(addAxiomLikesDirectorRange);
		manager.applyChange(addAxiomLikesTitleDomain);
		manager.applyChange(addAxiomLikesTitleRange);

		indSpectator = factory.getOWLNamedIndividual(spec);
		manager.applyChange(new AddAxiom(ontology, factory.getOWLClassAssertionAxiom(spectator, indSpectator)));
        
		saveOntology();
	}
	

	public void addRules(){
		
		SWRLVariable x = factory.getSWRLVariable(IRI.create(ontologyIRI + "#x"));
		SWRLVariable y = factory.getSWRLVariable(IRI.create(ontologyIRI + "#y"));
		SWRLVariable z = factory.getSWRLVariable(IRI.create(ontologyIRI + "#z"));
		
		OWLObjectProperty propActedOn = factory.getOWLObjectProperty(IRI.create(ontologyIRI + "#ActedOn"));
		OWLObjectProperty propLikesActor = factory.getOWLObjectProperty(IRI.create(ontologyIRI + "#LikesActor"));
		OWLObjectProperty propLikesTitle = factory.getOWLObjectProperty(IRI.create(ontologyIRI + "#LikesTitle"));
		
		SWRLObjectPropertyAtom propAtomActedOn = factory.getSWRLObjectPropertyAtom(propActedOn, x, y);
		SWRLObjectPropertyAtom propAtomLikesActor = factory.getSWRLObjectPropertyAtom(propLikesActor, z, x);
		SWRLObjectPropertyAtom propAtomLikesTitle = factory.getSWRLObjectPropertyAtom(propLikesTitle, z, y);
		
		Set<SWRLAtom> antecedent = new HashSet<SWRLAtom>();
		antecedent.add(propAtomActedOn);
		antecedent.add(propAtomLikesActor);
		SWRLRule rule = factory.getSWRLRule(antecedent, Collections.singleton(propAtomLikesTitle));
			
		manager.applyChange(new AddAxiom(ontology, rule));
		
		SWRLVariable a = factory.getSWRLVariable(IRI.create(ontologyIRI + "#a"));
		SWRLVariable b = factory.getSWRLVariable(IRI.create(ontologyIRI + "#b"));
		SWRLVariable c = factory.getSWRLVariable(IRI.create(ontologyIRI + "#c"));
		
		OWLObjectProperty propDirectedOn = factory.getOWLObjectProperty(IRI.create(ontologyIRI + "#DirectedOn"));
		OWLObjectProperty propLikesDirector = factory.getOWLObjectProperty(IRI.create(ontologyIRI + "#LikesDirector"));
		OWLObjectProperty propLikesTitleD = factory.getOWLObjectProperty(IRI.create(ontologyIRI + "#LikesTitle"));
		
		SWRLObjectPropertyAtom propAtomDirectedOn = factory.getSWRLObjectPropertyAtom(propDirectedOn, a, b);
		SWRLObjectPropertyAtom propAtomLikesDirector = factory.getSWRLObjectPropertyAtom(propLikesDirector, c, a);
		SWRLObjectPropertyAtom propAtomLikesTitleD = factory.getSWRLObjectPropertyAtom(propLikesTitleD, c, b);
		
		Set<SWRLAtom> antecedentD = new HashSet<SWRLAtom>();
		antecedentD.add(propAtomDirectedOn);
		antecedentD.add(propAtomLikesDirector);
		SWRLRule ruleD = factory.getSWRLRule(antecedentD, Collections.singleton(propAtomLikesTitleD));
		
		manager.applyChange(new AddAxiom(ontology, ruleD));
		
		saveOntology();
	}
	
	public void addFavActor(String act){
		System.out.println("Buscando...");
		try {
			addAxioms(baseURL+act);
		} catch (OWLOntologyCreationException e) {
			e.printStackTrace();
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	 	OWLIndividual actor = factory.getOWLNamedIndividual(IRI.create("#"+act));
	 	OWLObjectProperty likesActorProp = factory.getOWLObjectProperty(IRI.create(ontologyIRI + "#LikesActor"));
	 	
	 	OWLObjectPropertyAssertionAxiom a = factory.getOWLObjectPropertyAssertionAxiom(likesActorProp, indSpectator, actor);
	 	AddAxiom addAxiomChange = new AddAxiom(ontology, a);
	 	manager.applyChange(addAxiomChange); 
		
		saveOntology();
	}
	
	public OWLAxiom addFavActorName(String name){
		System.out.println("Buscando...");
		
		try {
			addAxioms(URLbyName+name);
			
			URL url = new URL(URLgetId+name);
			BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()));
	        String id = br.readLine();
			
	        OWLIndividual actor = factory.getOWLNamedIndividual(IRI.create("#"+id));
	        OWLObjectProperty likesActorProp = factory.getOWLObjectProperty(IRI.create(ontologyIRI + "#LikesActor"));
		 	
		 	OWLObjectPropertyAssertionAxiom assertion = factory.getOWLObjectPropertyAssertionAxiom(likesActorProp, indSpectator, actor);
		 	AddAxiom addAxiomChange = new AddAxiom(ontology, assertion);
		 	manager.applyChange(addAxiomChange);
		
		 	saveOntology();
		 	
		 	return addAxiomChange.getAxiom();
		 	
		} catch (OWLOntologyCreationException e) {
			e.printStackTrace();
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		return null;
	}

	public void addFavDirector(String dir){
		System.out.println("Buscando...");
		try {
			addAxioms(baseURL+dir);
		} catch (OWLOntologyCreationException e) {
			e.printStackTrace();
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	 	OWLIndividual director = factory.getOWLNamedIndividual(IRI.create("#"+dir));
	 	OWLObjectProperty likesDirectorProp = factory.getOWLObjectProperty(IRI.create(ontologyIRI + "#LikesDirector"));
	 	
	 	OWLObjectPropertyAssertionAxiom assertion = factory.getOWLObjectPropertyAssertionAxiom(likesDirectorProp, indSpectator, director);
	 	AddAxiom addAxiomChange = new AddAxiom(ontology, assertion);
	 	manager.applyChange(addAxiomChange); 
		
		saveOntology();
	}
	
	public OWLAxiom addFavDirectorName(String name){
		System.out.println("Buscando...");
		try {
			addAxioms(URLbyName+name);
			
			URL u = new URL(URLgetId+name);
	        InputStream is = u.openStream();
	        InputStreamReader isr = new InputStreamReader(is);
	        BufferedReader br = new BufferedReader(isr);
	        String id = br.readLine();
			
	        OWLIndividual director = factory.getOWLNamedIndividual(IRI.create("#"+id));
		 	OWLObjectProperty likesDirectorProp = factory.getOWLObjectProperty(IRI.create(ontologyIRI + "#LikesDirector"));
		 	
		 	OWLObjectPropertyAssertionAxiom assertion = factory.getOWLObjectPropertyAssertionAxiom(likesDirectorProp, indSpectator, director);
		 	AddAxiom addAxiomChange = new AddAxiom(ontology, assertion);
		 	manager.applyChange(addAxiomChange); 
		
		 	saveOntology();
		 	
		 	return addAxiomChange.getAxiom();
		 	
		} catch (OWLOntologyCreationException e) {
			e.printStackTrace();
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return null;
		
	}
	
	public void saveOntology(){
		
		try {
			manager.saveOntology(ontology, IRI.create(file.toURI()));
		} catch (OWLOntologyStorageException e) {
			System.out.println("Error guardando la ontologia");
			e.printStackTrace();
		}
	}
	
	public void removeAxiom(OWLAxiom ax){
		
		manager.removeAxiom(ontology, ax);
		saveOntology();
		
	}
	
	public void startReasoner(){
		
		reasonerFactory = new PelletReasonerFactory();
		
		reasoner = reasonerFactory.createNonBufferingReasoner(ontology);
		reasoner.precomputeInferences(InferenceType.CLASS_HIERARCHY);
		
		List<InferredAxiomGenerator<? extends OWLAxiom>> gens = new ArrayList<InferredAxiomGenerator<? extends OWLAxiom>>();
		gens.add(new InferredPropertyAssertionGenerator()); 
		
		InferredOntologyGenerator iog = new InferredOntologyGenerator(reasoner, gens);
		iog.fillOntology(manager, ontology);
		
		saveOntology();
	}
	
	public void recommendTitle(){
		
		OWLNamedIndividual spectator = factory.getOWLNamedIndividual(spec);
	 	OWLObjectProperty likesTitleProp= factory.getOWLObjectProperty(likesIRI);
	 	
	 	NodeSet<OWLNamedIndividual> movies = reasoner.getObjectPropertyValues(spectator, likesTitleProp);
	 	Set<OWLNamedIndividual> moviesValues = movies.getFlattened();
	 	System.out.println("\nPelicula recomendada para "+user+": ");
	 	
	 	List<OWLNamedIndividual> auxList = new ArrayList<OWLNamedIndividual>(moviesValues);
	 	
		Collections.shuffle(auxList);
	 	
		OWLDataProperty nameProp = factory.getOWLDataProperty(IRI.create("http://cunctus.itba.edu.ar:8002/IMDb#Name"));
		
		OWLNamedIndividual ind = auxList.get(0);
		Set<OWLLiteral> nameValuesNodeSet = reasoner.getDataPropertyValues(ind, nameProp);
		String recomendation = ((OWLLiteral)nameValuesNodeSet.toArray()[0]).getLiteral();
		
		System.out.println(recomendation);
		
	}
	
	public static void main(String[] args) throws FileNotFoundException, OWLOntologyCreationException, OWLOntologyStorageException{
		
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		System.out.println("Ingrese su nombre");
		try {
			String name = br.readLine();
			Recommender sugeridor = new Recommender(name);
			
			sugeridor.addSpecAxioms();
			sugeridor.addRules();
			
			boolean salir = false;
			OWLAxiom ax;
			
			sugeridor.startReasoner();
			
			while(!salir){
				System.out.println("\n\n1. Recomendar por actor\n2. Recomendar por director\n3. Salir\n");
				int op = Integer.valueOf(br.readLine());
				
				switch (op) {
				case 1:
					System.out.println("Ingrese su actor favorito");
					String actor = br.readLine();
					actor = actor.replace(' ', '_');
					
					ax = sugeridor.addFavActorName(actor);
					
					sugeridor.recommendTitle();
					sugeridor.removeAxiom(ax);
					break;
				case 2:
					System.out.println("Ingrese su director favorito");
					String dir = br.readLine();
					dir = dir.replace(' ', '_');
					
					ax = sugeridor.addFavDirectorName(dir);
					
					sugeridor.recommendTitle();
					sugeridor.removeAxiom(ax);
					break;
				case 3:
					salir = true;
					break;
				default:
					break;
				}
				
				
			}
			
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}
}
